using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using TPXSDK.NET.Share;

namespace TPXSDK.NET.GIS;

public enum AbsPosFormat
{
    Degree,   // 度格式
    DegMin,   // 度分格式
    DegMinSec // 度分秒格式
}

/// <summary>
/// 类似LLA位置格式，但是构造函数经纬度位置对调
/// </summary>
public class AbsPos
{
    #region 成员变量
    public double Lng { get; set; }
    public double Lat { get; set; }
    public double Alt { get; set; }
    #endregion

    public static readonly AbsPos Zero = new();
    
    [JsonConstructor]
    public AbsPos(double lng = 0d, double lat = 0d, double alt = 0d)
    {
        Lat = lat;
        Lng = lng;
        Alt = alt;
    }

    public AbsPos(string strLngLatAlt)
    {
        strLngLatAlt = strLngLatAlt.Trim();
        if (strLngLatAlt.StartsWith('(') ||
            strLngLatAlt.StartsWith('[') ||
            strLngLatAlt.EndsWith(')') ||
            strLngLatAlt.EndsWith(']'))
        {
            strLngLatAlt = strLngLatAlt[1..^1];
        }

        var segs = strLngLatAlt.Split(',');
        if (segs.Length >= 2)
        {
            var tmp = Parse(segs[0], segs[1]);
            Lat = tmp.Lat;
            Lng = tmp.Lng;
            Alt = segs.Length > 2 ? double.Parse(segs[2]) : 0d;
        }
        else
        {
            Lat = 0d;
            Lng = 0d;
            Alt = 0d;
        }
    }
    /// <summary>
    /// 检测是否为空值
    /// </summary>
    public static bool IsNullOrEmpty(AbsPos? pos)
    {
        return pos == null || pos.IsZero;
    }

    private const double Tolerance = 0.00000000001;

    /// <summary>
    /// 检测是否为零值
    /// </summary>
    public bool IsZero => Math.Abs(Lat) < Tolerance
                          && Math.Abs(Lng) < Tolerance
                          && Math.Abs(Alt) < Tolerance;

    /// <summary>
    /// 检测是否有效
    /// </summary>
    public bool IsValid => !(Lat > 90 || Lat < -90) && !(Lng > 180 || Lng < -180) && !IsZero;

    /// <summary>
    /// 转换为字符串，默认格式
    /// </summary>
    public override string ToString()
    {
        return $"({Lng:N7},{Lat:N7},{Alt:N4})";
    }

    /// <summary>
    /// 判断两个绝对坐标是否重合
    /// </summary>
    public bool IsEquals(AbsPos pos)
    {
        return Math.Abs(pos.Lng - Lng) < Tolerance
               && Math.Abs(pos.Lat - Lat) < Tolerance
               && Math.Abs(pos.Alt - Alt) < Tolerance
               && pos.GetType() == GetType();
    }

    #region 工具函数
    /// <summary>
    /// 根据偏NED坐标系移量，生成新的坐标点
    /// </summary>
    public AbsPos LocOffset(double ofsNorth, double ofsEast)
    {
        var prj = new MapProjection();
        prj.InitReference(Lat, Lng);
        var ret = prj.Reproject(ofsNorth, ofsEast);
        ret.Alt = Alt; // 不变更高度
        return ret;
    }

    /// <summary>
    /// 根据距离和偏航角，计算新位置点
    /// </summary>
    public AbsPos LocUpdate(double bearing, double dist)
    {
        double ofsNorth = dist * Math.Cos(DataUtils.Deg2Rad(bearing));
        double ofsEast = dist * Math.Sin(DataUtils.Deg2Rad(bearing));
        var ret = LocOffset(ofsNorth, ofsEast);
        ret.Alt = Alt; // 不变更高度
        return ret;
    }

    /// <summary>
    /// 计算两个绝对坐标的相对位置
    /// </summary>
    public RelPos LocDiff(AbsPos refLoc)
    {
        var prj = new MapProjection();
        prj.InitReference(refLoc.Lat, refLoc.Lng);
        var ret = prj.Project(Lat, Lng);
        ret.Z = Alt - refLoc.Alt;
        return ret;
    }

    /// <summary>
    /// 计算到目标位置距离，单位米
    /// </summary>
    public double Distance(AbsPos target)
    {
        if (IsNullOrEmpty(target))
        {
            return 0.0;
        }
        var pos = LocDiff(target);
        return Math.Sqrt(pos.X * pos.X + pos.Y * pos.Y);
    }

    /// <summary>
    /// 计算到目标点方位角，单位deg (0~360)
    /// </summary>
    public double Bearing(AbsPos target)
    {
        var latNowRad = DataUtils.Deg2Rad(Lat);
        var lonNowRad = DataUtils.Deg2Rad(Lng);

        var latNextRad = DataUtils.Deg2Rad(target.Lat);
        var lonNextRad = DataUtils.Deg2Rad(target.Lng);

        var dLon = lonNextRad - lonNowRad;

        /* conscious mix of double and float trig function to maximize speed and efficiency */
        var theta = Math.Atan2(Math.Sin(dLon) * Math.Cos(latNextRad), Math.Cos(latNowRad) * Math.Sin(latNextRad) - Math.Sin(latNowRad) * Math.Cos(latNextRad) * Math.Cos(dLon));

        return DataUtils.Wrap360(DataUtils.Rad2Deg(theta));
    }

    // 解析度分秒格式的字符串输入
    private static bool ParseFmtDMS(string strInput, string pattern, ref double deg)
    {
        var m = Regex.Match(strInput, pattern);
        if (m.Success)
        {
            try
            {
                int degrees = int.Parse(m.Groups[1].Value);
                int minutes = int.Parse(m.Groups[2].Value);
                double seconds = double.Parse(m.Groups[3].Value);
                deg = GeoUtils.DMSToDD(degrees, minutes, seconds);
            }
            catch { return false; }
        }

        return m.Success;
    }

    // 解析度分格式的字符串输入
    private static bool ParseFmtDM(string strInput, string pattern, ref double deg)
    {
        var m = Regex.Match(strInput, pattern);
        if (m.Success)
        {
            try
            {
                int degrees = int.Parse(m.Groups[1].Value);
                double minutes = double.Parse(m.Groups[2].Value);
                deg = GeoUtils.DMToDD(degrees, minutes);
            }
            catch { return false; }
        }
        return m.Success;
    }

    private static AbsPos Parse(string strLng, string strLat)
    {
        if (string.IsNullOrEmpty(strLat) || string.IsNullOrEmpty(strLng))
        {
            return Zero;
        }
        strLng = strLng.Trim();
        strLat = strLat.Trim();
        var latDeg = 0d;
        var lngDeg = 0d;
        var result = new AbsPos();
        //================================================================
        // 度 格式：
        //================================================================
        var matched = double.TryParse(strLat, out latDeg);

        var ptnDMS = @"^(?:(-?\d{1,3})[度° ])?(?:(\d{1,2})[分'′ ])?(?:(\d{1,2}(?:\.\d+)?)[秒""″ ])?$";
        //================================================================
        // 度分秒 格式：DD°MM'SS.SS" 例如：45°30'15.12"
        // 度分秒 格式：DD°MM'SS.SS″
        // 度分秒 格式：DD度MM分SS.SS秒"，例如：45度30分15.12秒
        //================================================================
        if (!matched)
        {
            matched = ParseFmtDMS(strLat, ptnDMS, ref latDeg);
        }

        var ptnDM = @"^(?:(-?\d{1,3})[°度 ])?(?:(\d{1,2}(?:\.\d+)?)[分'′ ])?$";
        //================================================================
        // 度分格式：DD°MM.MMMM'  Demo：45°30.1234'
        // 度分格式：DD度MM.MMMM分 Demo：45度30.1234分
        //================================================================
        if (!matched)
        {
            matched = ParseFmtDM(strLat, ptnDM, ref latDeg);
        }
        
        if (matched)
        {
            result.Lat = latDeg;
        }

        // 解析失败清理输出
        if (!matched)
        {
            return Zero;
        }

        //================================================================
        // =======================
        //================================================================
        matched = double.TryParse(strLng, out lngDeg);

        if (!matched)
        {
            matched = ParseFmtDMS(strLng, ptnDMS, ref lngDeg);
        }

        if (!matched)
        {
            matched = ParseFmtDM(strLng, ptnDM, ref lngDeg);
        }
        
        if (matched)
        {
            result.Lng = lngDeg;
        }

        return matched ? result : Zero;
    }

    /// <summary>
    /// 根据参数转换为不同字符串格式
    /// </summary>
    public static string Str(double val, AbsPosFormat fmt)
    {
        var fraction = new double[3];
        fraction[0] = Math.Floor(val);
        fraction[1] = (val - fraction[0]) * 60;
        switch (fmt)
        {
            case AbsPosFormat.DegMin:
                return $"{fraction[0]:00}:{fraction[1]:00.0000}";
            case AbsPosFormat.DegMinSec:
                fraction[2] = (fraction[1] - Math.Floor(fraction[1])) * 60;
                fraction[1] = Math.Floor(fraction[1]);
                return $"{fraction[0]:00}°{fraction[1]:00}′{fraction[2]:00.00}″";
            case AbsPosFormat.Degree:
                return $"{val:F7}";
            default:
                return string.Empty;
        }
    }
    #endregion
}
